﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace KAImage
{
    /// <summary>
    /// 曲线控制点的动作
    /// </summary>
    public enum CurvePointAction : int
    {
        None = -99,            // 没有动作或者不应该执行任何动作
        Clear = -1,             
        Add = 0,
        Remove = 1,
        Update = 2,
    }

    public class SplineCurvePoint : IComparable
    {
        public double X = 0;
        public double Y = 0;

  
        public SplineCurvePoint()
        {
            
        }

        public SplineCurvePoint(Point p)
        {
            X = p.X;
            Y = p.Y;
        }

        public SplineCurvePoint(int x, int y)
        {
            X = x;
            Y = y;
        }

        public SplineCurvePoint(double x, double y)
        {
            X = x;
            Y = y;
        }

        public int CompareTo(object obj)
        {
            SplineCurvePoint other = obj as SplineCurvePoint;
            return this.X.CompareTo(other.X);
        }
    }

    /// <summary>
    /// RGB颜色调整: 样条插值曲线算法
    /// </summary>
    public class Spline
    {
        /// <summary>
        /// 当前选中的关键点索引
        /// </summary>
        private int _focusIndex = -1;
        public int FocusIndex
        {
            get { return _focusIndex; }
            set { _focusIndex = value; }
        }

        /// <summary>
        /// 整个图层的高度
        /// </summary>
        private readonly int H = 510;

        /// <summary>
        /// 逻辑曲线中的原始控制点
        /// </summary>
        private List<Point> _originalPoints = new List<Point>();
        public List<Point> OriginalPoints
        {
            get { return _originalPoints; }
        }

        /// <summary>
        /// 原始控制点映射到实际画布中的点
        /// </summary>
        private List<Point> _originalDrawPoints = new List<Point>();
        public List<Point> OriginalDrawPoints
        {
            get { return _originalDrawPoints; }
        }

        /// <summary>
        /// 实际画布中绘制曲线需要的点集
        /// </summary>
        private List<Point> _drawingPoints = new List<Point>();
        public List<Point> ListDrawingPoints
        {
            get { return _drawingPoints; }
        }

        /// <summary>
        /// 输入的点
        /// </summary>
        private SplineCurvePoint[] points = null;

        /// <summary>
        /// 输入的点数
        /// </summary>
        private int IN = 0;

        /// <summary>
        /// 需要插值的点数
        /// </summary>
        private const int CN = 254;

        /// <summary>
        /// 需要插值X集合 X∈[1, 254]
        /// </summary>
        private double[] insertSrc = new double[254];

        /// <summary>
        /// 输出的插值结果, X∈[1, 254]
        /// </summary>
        private double[] insertRes = new double[254];

        /// <summary>
        /// 计算结果通过访问数组映射
        /// </summary>
        private int[] mapping = new int[256];
        public int[] Mapping
        {
            get { return mapping; }
        }

        /// <summary>
        /// 指示线条是否处于激活可编辑的状态
        /// </summary>
        private bool _inActiveEditing = false;
        public bool ActiveEditing
        {
            get { return _inActiveEditing; }
        }

        public Color FrontColor = Color.Black;

        public Brush ColorBrush = Brushes.Black;

        public bool IsEmpty
        {
            get { return _originalPoints.Count == 0 || ListDrawingPoints.Count == 0; }
        }

        public static double ChinaRound(double value, int decimals)
        {
            if (value < 0)
            {
                return Math.Round(value + 5 / Math.Pow(10, decimals + 1), decimals, MidpointRounding.AwayFromZero);
            }
            else
            {
                return Math.Round(value, decimals, MidpointRounding.AwayFromZero);
            }
        }

        public static int GetPointDistance(Point p0, Point p1)
        {
            double p = Math.Sqrt(Math.Pow(p1.Y - p0.Y, 2) + Math.Pow(p1.X - p0.X, 2));
            int d = (int)Math.Round(p);
            return d;
        }

        public Spline()
        {
            for (int i = 0; i < mapping.Length; i++)
            {
                mapping[i] = i;
            }
        }

        /// <summary>
        /// 使用新的输入点集重新构造对象
        /// </summary>
        public void Reset(List<Point> listPoints, bool creatDrawPoints = true)
        {
            Clear();
            if (listPoints == null || listPoints.Count == 0)
            {
                return;
            }
            _inActiveEditing = true;
            _originalPoints.AddRange(listPoints);
            if (creatDrawPoints)
            {
                CalculateCurvePoints();    
            }
            else
            {
                CalculateMapping();
            }
        }

        public void Clear()
        {
            _focusIndex = -1;
            Array.Clear(mapping, 0, mapping.Length);
            Array.Clear(insertRes, 0, insertRes.Length);
            _originalPoints.Clear();
            _originalDrawPoints.Clear();
            _drawingPoints.Clear();
            for (int i = 0; i < mapping.Length; i++)
            {
                mapping[i] = i;
            }
            _inActiveEditing = false;
        }

        /// <summary>
        /// 移除选中的关键点
        /// </summary>
        public void RemoveControlPoint()
        {
            if (_focusIndex < 0 || IsEmpty)
                return;
            if (_originalPoints.Count > 1)
            {
                _originalPoints.RemoveAt(_focusIndex);
                if (_focusIndex == _originalPoints.Count)
                {
                    _focusIndex--;
                }
            }
            else if (_originalPoints.Count == 1)
            {
                Clear();
            }
        }

        public void AddControlPoint(Point p)
        {
            if(!_inActiveEditing)
            {
                _inActiveEditing = true;
            }
            _originalPoints.Add(p);
            _focusIndex = _originalPoints.Count - 1;
        }

        public void SetControlPoint(Point p)
        {
            if (!_inActiveEditing)
            {
                _inActiveEditing = true;
            }
            _originalPoints[_focusIndex] =p;
        }

        /// <summary>
        /// 计算关键点的位置
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public CurvePointAction CalculateCurvePointAction(Point p)
        {
            if (p.X > 256 && p.X < 0)
            {
                return CurvePointAction.None;
            }
            if (_originalDrawPoints.Count == 0)
            {
                if (Math.Abs(Mapping[p.X] - p.Y) < 20)
                {
                    return CurvePointAction.Add;
                }
                else
                {
                    return CurvePointAction.None;
                }
            }
            for (int i = 0; i < _originalPoints.Count; i++)
            {
                // 根据PS曲线算法的规定，相邻两个控制点的水平距离必须大于等于4
                if (Math.Abs(_originalPoints[i].X - p.X) <= 4)
                {
                    // 判断该点是否是临近点，临近点视为选中最近的一个点
                    if (GetPointDistance(p, _originalPoints[i]) < 5)
                    {
                        _focusIndex = i;
                        return CurvePointAction.Update;
                    }
                    else // 如果不是临近点，该点任何操作无效：既不能修改，也不能添加
                    {
                        return CurvePointAction.None;
                    }
                }                               
            }

            // 如果改点不是临近点，但是位于曲线附近，视为可以作为新的控制点添加到曲线中
            if (Math.Abs(Mapping[p.X] - p.Y) < 20)
            {
                // 曲线最多只能拥有14个控制点
                if (OriginalPoints.Count < 14)
                {
                    return CurvePointAction.Add;
                }               
            }

            return CurvePointAction.None;
        }

        private void CalculateOriginalDrawPoints()
        {
            _originalDrawPoints.Clear();
            for (int i = 0; i < _originalPoints.Count; i++)
            {
                int x = 2 * _originalPoints[i].X;
                int y = H - _originalPoints[i].Y * 2;
                _originalDrawPoints.Add(new Point(x, y));
            }
        }

        private void CalculateDrawingPoints()
        {
            _drawingPoints.Clear();
            int x = 0, y = 0;
            for (int i = 0; i < Mapping.Length; i++)
            {
                x = 2 * i;
                y = H - Mapping[i] * 2;
                _drawingPoints.Add(new Point(x, y));
            }
        }

        public void CalculateMapping()
        {
            CreatCurveControlPoints();
            Insert2();
            CheckResults();          
        }

        public void CalculateCurvePoints()
        {
            CreatCurveControlPoints();
            Insert2();
            CheckResults();      
            CalculateOriginalDrawPoints();
            CalculateDrawingPoints();
        }

        private void CreatCurveControlPoints()
        {
            for (int i = 0; i < CN; i++)
            {
                insertSrc[i] = i + 1;
            }
            IN = _originalPoints.Count + 2;
            points = new SplineCurvePoint[IN];
            points[0] = new SplineCurvePoint(0D, 0D);
            for (int i = 0; i < _originalPoints.Count; i++)
            {
                Point p = _originalPoints[i];
                points[i + 1] = new SplineCurvePoint(p.X, p.Y);
            }
            points[IN - 1] = new SplineCurvePoint(255D, 255D);
            Array.Sort(points);
        }

        private void CheckResults()
        {
            mapping[0] = 0;           
            for (int i = 0; i < insertRes.Length; i++)
            {
                mapping[i + 1] = (int)Math.Floor(insertRes[i] + 0.5D);
                if (mapping[i + 1] < 0)
                {
                    mapping[i + 1] = 0;
                }
                if (mapping[i + 1] > 255)
                {
                    mapping[i + 1] = 255;
                }
            }
            mapping[255] = 255;
        }

        /// <summary>
        /// 三次样条插值
        /// </summary>
        /// <param name="points">排序好的数</param>
        /// <param name="xs">需要计算的插值点</param>
        /// <param name="chf">写1</param>
        /// <returns>返回计算好的数值</returns>
        private void Insert()
        {
            double[] h = new double[IN];
            double[] f = new double[IN];
            double[] l = new double[IN];
            double[] v = new double[IN];
            double[] g = new double[IN];
            for (int i = 0; i < IN - 1; i++)
            {
                h[i] = points[i + 1].X - points[i].X;
                f[i] = (points[i + 1].Y - points[i].Y) / h[i];
            }
            for (int i = 1; i < IN - 1; i++)
            {
                l[i] = h[i] / (h[i - 1] + h[i]);
                v[i] = h[i - 1] / (h[i - 1] + h[i]);
                g[i] = 3 * (l[i] * f[i - 1] + v[i] * f[i]);
            }
            double[] b = new double[IN];
            double[] tem = new double[IN];
            double[] m = new double[IN];
            double f0 = (points[0].Y - points[1].Y) / (points[0].X - points[1].X);
            double fn = (points[IN - 1].Y - points[IN - 2].Y) / (points[IN - 1].X - points[IN - 2].X);
            b[1] = v[1] / 2;
            for (int i = 2; i < IN - 2; i++)
            {
                b[i] = v[i] / (2 - b[i - 1] * l[i]);
            }
            tem[1] = g[1] / 2;
            for (int i = 2; i < IN - 1; i++)
            {
                tem[i] = (g[i] - l[i] * tem[i - 1]) / (2 - l[i] * b[i - 1]);
            }
            m[IN - 2] = tem[IN - 2];
            for (int i = IN - 3; i > 0; i--)
            {
                m[i] = tem[i] - b[i] * m[i + 1];
            }

            m[0] = 3 * f[0] / 2.0;
            m[IN - 1] = fn;   
      
            for (int i = 0; i < CN; i++)
            {
                int j = 0;
                for (j = 0; j < IN; j++)
                {
                    if (insertSrc[i] < points[j].X)
                        break;
                }
                j = j - 1;

                if (j == -1 || j == points.Length - 1)
                {
                    if (j == -1)
                    {
                        throw new SplineInsertException("插值下边界超出");
                    }
                    if (j == points.Length - 1 && insertSrc[i] == points[j].X)
                    {
                        insertRes[i] = points[j].Y;
                    }
                    else
                    {
                        throw new SplineInsertException("插值下边界超出");
                    }
                }
                else
                {
                    double p1 = (insertSrc[i] - points[j + 1].X) / (points[j].X - points[j + 1].X);
                    p1 = p1 * p1;
                    double p2 = (insertSrc[i] - points[j].X) / (points[j + 1].X - points[j].X);
                    p2 = p2 * p2;
                    double p3 = p1 * (1 + 2 * (insertSrc[i] - points[j].X) / (points[j + 1].X - points[j].X)) * points[j].Y + p2 * (1 + 2 * (insertSrc[i] - points[j + 1].X) / (points[j].X - points[j + 1].X)) * points[j + 1].Y;
                    double p4 = p1 * (insertSrc[i] - points[j].X) * m[j] + p2 * (insertSrc[i] - points[j + 1].X) * m[j + 1];
                    p4 = p4 + p3;
                    insertRes[i] = p4;
                }
            }
        }

        private void Insert2()
        {
            double[] x = points.Select(r => r.X).ToArray();
            double[] y = points.Select(r => r.Y).ToArray();
            double[] dy = new double[IN];
            Array.Clear(dy, 0, IN);
            dy[0] = -0.5;

            double[] ddy = new double[IN];
            Array.Clear(ddy, 0, IN);
           
            double h1 = 0.0D;
            double[] s = new double[IN];
            double h0 = x[1] - x[0];
            s[0] = 3.0 * (y[1] - y[0]) / (2.0 * h0) - ddy[0] * h0 / 4.0;
            for (int j = 1; j <= IN - 2; j++)
            {
                h1 = x[j + 1] - x[j];
                double alpha = h0 / (h0 + h1);
                double beta = (1.0 - alpha) * (y[j] - y[j - 1]) / h0;
                beta = 3.0 * (beta + alpha * (y[j + 1] - y[j]) / h1);
                dy[j] = -alpha / (2.0 + (1.0 - alpha) * dy[j - 1]);
                s[j] = (beta - (1.0 - alpha) * s[j - 1]);
                s[j] = s[j] / (2.0 + (1.0 - alpha) * dy[j - 1]);
                h0 = h1;
            }
            dy[IN - 1] = (3.0 * (y[IN - 1] - y[IN - 2]) / h1 + ddy[IN - 1] * h1 / 2.0 - s[IN - 2]) / (2.0 + dy[IN - 2]);
            for (int j = IN - 2; j >= 0; j--)
            {
                dy[j] = dy[j] * dy[j + 1] + s[j];
            }
            for (int j = 0; j <= IN - 2; j++)
            {
                s[j] = x[j + 1] - x[j];
            }
            for (int j = 0; j <= IN - 2; j++)
            {
                h1 = s[j] * s[j];
                ddy[j] = 6.0 * (y[j + 1] - y[j]) / h1 - 2.0 * (2.0 * dy[j] + dy[j + 1]) / s[j];
            }
            h1 = s[IN - 2] * s[IN - 2];
            ddy[IN - 1] = 6.0 * (y[IN - 2] - y[IN - 1]) / h1 + 2.0 * (2.0 * dy[IN - 1] + dy[IN - 2]) / s[IN - 2];
            for (int i = 0; i <= IN - 2; i++)
            {
                h1 = 0.5 * s[i] * (y[i] + y[i + 1]);
                h1 = h1 - s[i] * s[i] * s[i] * (ddy[i] + ddy[i + 1]) / 24.0;
            }
            for (int j = 0; j <= CN - 1; j++)
            {
                int i;
                if (insertSrc[j] >= x[IN - 1])
                {
                    i = IN - 2;
                }
                else
                {
                    i = 0;
                    while (insertSrc[j] > x[i + 1])
                    {
                        i = i + 1;
                    }
                }
                h1 = (x[i + 1] - insertSrc[j]) / s[i];
                h0 = h1 * h1;
                insertRes[j] = (3.0 * h0 - 2.0 * h0 * h1) * y[i];
                insertRes[j] = insertRes[j] + s[i] * (h0 - h0 * h1) * dy[i];
                h1 = (insertSrc[j] - x[i]) / s[i];
                h0 = h1 * h1;
                insertRes[j] = insertRes[j] + (3.0 * h0 - 2.0 * h0 * h1) * y[i + 1];
                insertRes[j] = insertRes[j] - s[i] * (h0 - h0 * h1) * dy[i + 1];
            }        
        }
    }
}
